﻿using System;
using System.Diagnostics;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Engines.MCMC.PSS
{
    public partial class PssPathSampler : PathSamplerBase
    {
        internal RgbSpectrum Throughput, oldContribution;
        protected int depth, tracedShadowRayCount, mutationsCount;
        protected float pathWeight;
        protected bool specularBounce;
        protected SurfaceIntersectionData hitInfo = null;
        protected double oldx, oldy, c_x, c_y;

        public UInt64 PathExpression;
        protected RayEngineScene scene;
        protected bool largeStep;

        public new PsSample Sample;
        public int MaxRaysPerPath
        {
            get;
            set;
        }

        public override void InitPath(IPathProcessor buffer)
        {
            base.InitPath(buffer);
            MaxRaysPerPath = 1;
            this.scene = pathIntegrator.Scene;
            var pssSampler = (PsMltSampler)this.pathIntegrator.Sampler;

            this.Radiance = new RgbSpectrum(0f);
            this.Throughput = new RgbSpectrum(1f);
            if (this.Sample == null || this.Sample.LargeStep || pssSampler.BurnInPhase ||this.Sample.MutationsCount > (pssSampler.MaxMutations))
            {
                this.Sample = (PsSample)pssSampler.GetSample(this.Sample);
            }
            else
            {
                this.Sample.imageX = MC.Mutate(this.Sample.imageX, pssSampler.NextFloat(), pssSampler.NextFloat(),2.0f/(2000),0.1f);
                this.Sample.imageY = MC.Mutate(this.Sample.imageY, pssSampler.NextFloat(), pssSampler.NextFloat(), 2.0f / (2000), 0.1f);
                pssSampler.TotalSamples++;
            }
            IRay ray;
            pathIntegrator.Scene.Camera.GetRay(Sample.imageX, Sample.imageY, out ray);
            this.PathRay = (RayData)ray;
            this.RayIndex = -1;
            this.pathWeight = 1.0f;
            this.tracedShadowRayCount = 0;
            this.depth = 0;
            this.specularBounce = true;
        }


     
        public override bool FillRayBuffer(RayBuffer rayBuffer)
        {
            var leftSpace = rayBuffer.LeftSpace();
            if (leftSpace < MaxRaysPerPath)
                return false;
            RayIndex = rayBuffer.AddRay(ref PathRay);
            return true;
        }
        public override void Advance(RayBuffer rayBuffer, SampleBuffer consumer)
        {
            int currentTriangleIndex = 0;
#if VERBOSE

            try {
#endif

            base.Advance(rayBuffer, consumer);

            var rayHit = rayBuffer.rayHits[RayIndex];

            depth++;
            var missed = rayHit.Index == 0xffffffffu;
            var wo = -PathRay.Dir;

            if (missed || depth > scene.MaxPathDepth)
            {
                if (specularBounce && missed)
                {
                    Radiance += this.scene.SampleEnvironment(ref wo) * Throughput;
                }
                Splat(consumer);

                return;
            }

            // Something was hit
            if (hitInfo == null)
            {
                hitInfo = SurfaceSampler.GetIntersection(ref PathRay, ref rayHit);
            }
            else
            {
                SurfaceSampler.GetIntersection(ref PathRay, ref rayHit, ref hitInfo);
            }
            currentTriangleIndex = (int)rayHit.Index;

            //If Hit light)
            if (hitInfo.IsLight)
            {
                {
                    var lt = scene.GetLightByIndex(currentTriangleIndex);
                    if (lt != null)
                    {
                        var le = (RgbSpectrum)(RgbSpectrumInfo)(lt.Le(ref PathRay.Dir));
                        Radiance += Throughput * le;
                    }
                }
                Splat(consumer);

                return;
            }

            var hitPoint = PathRay.Point(rayHit.Distance);

            tracedShadowRayCount = 0;

            float fPdf;
            Vector wi;

            var f = hitInfo.MMaterial.Sample_f(ref wo, out wi, ref hitInfo.Normal, ref hitInfo.ShadingNormal, ref Throughput,
                                                      Sample.GetLazyValue(), Sample.GetLazyValue(),
                                                      Sample.GetLazyValue(), ref hitInfo.TextureData,
                                                      out fPdf, out specularBounce);

            if ((fPdf <= 0.0f) || f.IsBlack())
            {
                Splat(consumer);
                return;
            }
            pathWeight *= fPdf;
            Throughput *= (f * hitInfo.Color) / fPdf;

            if (depth > scene.MaxPathDepth)
            {
                float prob = Math.Max(Throughput.Filter(), scene.RussianRuletteImportanceCap);
                if (prob >= Sample.GetLazyValue())
                {
                    Throughput /= prob;
                    pathWeight *= prob;
                }
                else
                {

                    Splat(consumer);
                    return;
                }
            }

            PathRay.Org = hitPoint;
            PathRay.Dir = wi.Normalize();

#if VERBOSE
            }
            catch (Exception ex) {
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Advance path exception");
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine("Error triangle {0}", currentTriangleIndex);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.Message);
                RayDen.Library.Components.SystemComponents.Tracer.TraceLine(ex.StackTrace);

            }
#endif
        }
    }
}
